-- lan_mgr.lua

require ("core.Store")

local tinsert = table.insert
local tsort   = table.sort

local BROADCAST_IP = "224.1.1.2"
local BROADCAST_PORT = 11001

local MAX_RECV_PACKAGE_SIZE = 5242880

LanMgr = singleton()
local prop = property(LanMgr)
prop:accessor("host_list", {})
prop:accessor("udp_client", nil)
prop:accessor("target_host", "")
prop:accessor("target_port", 0)
prop:accessor("seq_id", 0)
prop:accessor("package_cache", {})
prop:accessor("send_queue", {})             -- 发送队列
prop:accessor("boardcast_seq_map", {})      -- 广播映射

function LanMgr:__init()
    self.udp_client = luabus.udp(true)
    self.udp_client.set_buff_size(MAX_RECV_PACKAGE_SIZE)
end

-- 写入文件
function LanMgr:write_file(file_name, contents, base64)
    os.remove(file_name)
    local file = io.open(file_name, "wb")
    if not file then
        print("[LanMgr][write_file] file_name:"..file_name)
        return false
    end
    if not base64 then
        for _,content in ipairs(contents) do
            file:write(content)
        end
    else
        local all_content = ""
        for _,content in ipairs(contents) do
            all_content = all_content .. content
        end
        all_content = ssl.b64_decode(all_content)
        file:write(all_content)
    end
    print("write_file file_name:"..file_name.." done")
    contents = nil
    file:close()
    return true
end

function LanMgr:bind_target(host, port)
    self.target_host = host
    self.target_port = port and port or BROADCAST_PORT
    if not self.target_port or self.target_port == 0 then
        self.target_port = BROADCAST_PORT
    end
    print("[LanMgr][bind_target] target_host:"..self.target_host.." target_port:"..self.target_port)
end

-- 添加发送队列
function LanMgr:add_send_queue(cmd_id, data, broadcast)
    local send_data = {
        cmd_id = cmd_id,
        data = data or {},
        seq_id = self.seq_id
    }
    tinsert(self.send_queue, {
        data = send_data,
        broadcast = broadcast or false
    })
end

-- 添加包缓存
function LanMgr:add_package_cache(cmd_id, broadcast, callback, timeout_sec, progress_cb, is_anim)
    self.package_cache[self.seq_id] = {
        cmd_id = cmd_id,
        callback = callback,
        progress_cb = progress_cb,
        recv_cache = {},
        recv_map = {},
        broadcast = broadcast or false,
        send_time = 0,
        timeout_sec = timeout_sec or 6
    }
    self.seq_id = self.seq_id + 1
    if not broadcast and is_anim then
        g_event_mgr:notifyListener("onRequestSent")
    end
    return self.seq_id
end

function LanMgr:del_package_cache(seq_id)
    local cache = self.package_cache[seq_id]
    if not cache then
        return
    end
    self.package_cache[seq_id] = nil
    g_event_mgr:notifyListener("onRequestReceipt")
end

-- 广播数据
function LanMgr:boardcast(cmd_id, data, callback, timeout_sec)
    self:add_send_queue(cmd_id, data, true)
    self:add_package_cache(cmd_id, true, callback, timeout_sec)
    if not self.boardcast_seq_map[cmd_id] then
        self.boardcast_seq_map[cmd_id] = {}
    end
    self.boardcast_seq_map[cmd_id][self.seq_id-1] = true
end

-- 发送数据
function LanMgr:send(cmd_id, data, callback, timeout_sec)
    self:add_send_queue(cmd_id, data)
    self:add_package_cache(cmd_id, false, callback, timeout_sec, nil, true)
end

function LanMgr:send_no_anim(cmd_id, data, callback, timeout_sec)
    self:add_send_queue(cmd_id, data)
    self:add_package_cache(cmd_id, false, callback, timeout_sec, nil, false)
end

-- 下载文件
function LanMgr:down_file(cmd_id, data, callback, progress_cb)
    self:add_send_queue(cmd_id, data)
    self:add_package_cache(cmd_id, false, callback, 6, progress_cb, true)
end

function LanMgr:recv_data(time)
    if not next(self.package_cache) then
        return
    end
    while true do
        local ok, buf = self.udp_client.recv()
        if ok then
            local recv_data = json.decode(buf)
            local clien_seq_id = recv_data.clien_seq_id
            local cache = self.package_cache[clien_seq_id]
            if not cache then
                goto continue
            end
            -- print("[LOG]recv package="..recv_data.package_max_id.."-"..recv_data.package_id.." cmd_id:"..cache.cmd_id)
            cache.send_time = time
            local down_file = recv_data.down_file
            if cache.recv_map[recv_data.package_id] then
                goto continue
            end
            cache.recv_cache[recv_data.package_id] = recv_data
            cache.recv_map[recv_data.package_id] = 1
            if recv_data.package_max_id > 1 and cache.progress_cb then
                cache.progress_cb(recv_data.package_max_id, #cache.recv_cache)
            end
            local done = false
            local err = nil
            if recv_data.msg ~= "success" then
                err = recv_data.msg
                g_event_mgr:notifyListener("onWarnTip", err)
            end
            if not down_file and recv_data.package_max_id == 1 then
                if cache.callback then
                    cache.callback(err, json.decode(recv_data.data))
                end
                done = true
            -- 合包(需要处理seq_id 广播回包归属混乱的问题)
            elseif #cache.recv_cache == recv_data.package_max_id then
                for i=#cache.recv_cache, 1, -1 do
                    if not cache.recv_cache[i] then
                        table.remove(cache.recv_cache, i)
                        print("丢包啦 i="..i.." cmd_id="..cache.cmd_id)
                    end
                end
                -- 包排序
                tsort(cache.recv_cache, function(a, b)
                    return a.package_id < b.package_id
                end)
                local content = ""
                local file_info = {
                    file_name = down_file and recv_data.file_name,
                    contents = {}
                }
                for _,item in ipairs(cache.recv_cache) do
                    if not down_file then
                        content = content .. item.data
                    else
                        tinsert(file_info.contents, item.data)
                    end
                end
                if not down_file then
                    content = json.decode(content)
                end
                if cache.callback then
                    cache.callback(err, down_file and file_info or content)
                end
                done = true
            end
            if done then
                cache.recv_cache = {}
                cache.recv_map = {}
                -- 处理结束,清空映射(广播消息除外)
                if not cache.broadcast then
                    self:del_package_cache(clien_seq_id)
                end
            end
        else
            if buf ~= "EWOULDBLOCK" then
                self:recv_error()
                print("[LanMgr][recv_data] recv failed buf:"..buf)
                g_event_mgr:notifyListener("onWarnTip", "网络中断")
            else
                break
            end
        end
        :: continue ::
    end
end

function LanMgr:send_data(time)
    if not next(self.send_queue) then
        return
    end
    for _,item in ipairs(self.send_queue) do
        local send_buf = json.encode(item.data)
        local host = self.target_host
        local port = self.target_port
        if item.broadcast then
            host = BROADCAST_IP
            port = BROADCAST_PORT
        end
        local seq_id = item.data.seq_id
        local cache = self.package_cache[seq_id]
        if cache then
            cache.send_time = time
        end
        self.udp_client.send(send_buf, #send_buf, host, port)
    end
    self.send_queue = {}
end

function LanMgr:time_out(time)
    if not next(self.package_cache) then
        return
    end
    for seq_id,cache in pairs(self.package_cache) do
        if cache.send_time ~= 0 and time - cache.send_time > cache.timeout_sec then
            if cache.callback then
                if not cache.broadcast then
                    cache.callback("timeout")
                else
                    cache.callback("done")
                end
            end
            self:del_package_cache(seq_id)
        end
    end
end

function LanMgr:recv_error()
    if not next(self.package_cache) then
        return
    end
    for seq_id,cache in pairs(self.package_cache) do
        if cache.callback then
            if not cache.broadcast then
                cache.callback("timeout")
            else
                cache.callback("done")
            end
        end
        self:del_package_cache(seq_id)
    end
end

function LanMgr:update(time)
    self:send_data(time)
    self:recv_data(time)
    self:time_out(time)
end
g_lan_mgr = LanMgr()

